﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography.X509Certificates;
using System.Runtime.InteropServices;
using System.Net.Mail;
using CDO;
using ADODB;
using SEO;

namespace NHINDirect
{
    public enum MessageClassification
    {
        Reject = 0,
        Outgoing = 1,
        Incoming = 2
    }
    
    public class SmtpAgentImpl
    {
        NHINDAgent m_agent;
        LogWriter m_log;

        public SmtpAgentImpl()
        {

        }

        public NHINDAgent Agent
        {
            get
            {
                this.VerifyInitialized();
                return m_agent;
            }
        }
        
        public LogWriter Log
        {
            get
            {
                return this.m_log;
            }
        }
        
        //----------------------------------
        //
        //  Initialization
        //  We'll add more variations here
        //
        //----------------------------------

        public void Init(string domain, string logFolderPath)
        {
            this.Init(domain, logFolderPath, "smtpAgent");
        }

        public void Init(string domain, string logFolderPath, string logFileName)
        {
            this.m_log = new LogWriter(logFolderPath, logFileName, "log", false);

            this.WriteLog("Init_Begin");
            try
            {
                //
                // Initialize the NHINDAgent with default Certificate Stores
                //      Local Computer\NHINDAnchors
                //      Local Computer\NHINDPrivate
                //      Local Computer\NHINDExternal
                //
                // Once setup, you can view these stores using the CertManager MMC snapin
                //
                this.m_agent = new NHINDAgent(domain);
                
                this.WriteLog("Init_Complete");
            }
            catch (Exception error)
            {
                this.WriteLog("Init_Failed");
                this.WriteError(error);
                throw;
            }
        }

        void VerifyInitialized()
        {
            if (m_agent == null)
            {
                throw new InvalidOperationException("Not initialized");
            }
        }
        
        //----------------------------------
        //
        //  SMTP Message Processing
        //
        //----------------------------------
        
        public MessageClassification ClassifyMessage(CDO.Message cdoMessage)
        {
            if (cdoMessage == null)
            {
                throw new ArgumentNullException();
            }
            
            this.VerifyInitialized();
            
            
            if (IsContentType(cdoMessage, Protocol.EncryptedContentMediaType, false) || IsContentType(cdoMessage, Protocol.EncryptedContentMediaTypeAlternative, false))
            {
                //
                // Encrypted, so we must decrypt
                //
                return MessageClassification.Incoming;
            }
            //
            // Non encrypted message. We'll only encrypt if it is from within the domain
            //
            if (IsDomainMatch(cdoMessage.From, this.Agent.Domain))
            {
                return MessageClassification.Outgoing;
            }
            
            return MessageClassification.Reject;
        }
        
        public void ProcessMessage(CDO.Message cdoMessage)
        {
            this.VerifyInitialized();
            
            this.WriteLog("ProcessMessage");
            MessageClassification messageType = this.ClassifyMessage(cdoMessage);
            switch(messageType)
            {
                case MessageClassification.Reject:
                    throw new TrustException(TrustError.UntrustedMessage);

                case MessageClassification.Outgoing:
                    this.ProcessOutgoing(cdoMessage);
                    break;
                    
                case MessageClassification.Incoming:
                    this.ProcessIncoming(cdoMessage);
                    break;
            }
        }

        public void ProcessIncoming(CDO.Message cdoMessage)
        {
            this.WriteLog("ProcessIncoming");
            this.ProcessMessage(cdoMessage, this.Agent.ProcessIncoming);
        }
        
        public string ProcessIncoming(string text)
        {
            this.WriteLog("ProcessIncoming");
            return this.ProcessMessage(text, this.Agent.ProcessIncoming);
        }

        public void ProcessOutgoing(CDO.Message cdoMessage)
        {
            this.WriteLog("ProcessOutgoing");
            this.ProcessMessage(cdoMessage, this.Agent.ProcessOutgoing);
        }

        public string ProcessOutgoing(string text)
        {
            this.WriteLog("ProcessOutgoing");
            return this.ProcessMessage(text, this.Agent.ProcessOutgoing);
        }

        void ProcessMessage(CDO.Message cdoMessage, Func<string, string> processor)
        {
            try
            {
                string messageText = this.GetMessageRaw(cdoMessage);
                messageText = processor(messageText);
                this.SetMessageRaw(cdoMessage, messageText, true);
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        string ProcessMessage(string messageText, Func<string, string> processor)
        {
            try
            {
                return processor(messageText);
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        static string GetContentType(CDO.Message cdoMessage)
        {
            return (string)cdoMessage.Fields["urn:schemas:mailheader:content-type"].Value;
        }

        static bool IsContentType(CDO.Message cdoMessage, string contentType, bool exactMatch)
        {
            string messageContentType = GetContentType(cdoMessage);
            if (string.IsNullOrEmpty(messageContentType))
            {
                return false;
            }

            if (exactMatch)
            {
                return messageContentType.Equals(contentType, StringComparison.OrdinalIgnoreCase);
            }

            return (messageContentType.IndexOf(contentType, StringComparison.OrdinalIgnoreCase) >= 0);
        }
        
        static bool IsDomainMatch(string emailAddress, string domain)
        {
            MailAddress address = new MailAddress(emailAddress);
            return address.DomainEquals(domain);
        }

        //----------------------------------
        //
        //  CDO Helpers...
        //
        //----------------------------------
        public CDO.Message CreateMessageFromFile(string filePath)
        {
            try
            {
                CDO.Message message = new CDO.Message();
                message.GetStream().LoadFromFile(filePath);
                return message;
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        public CDO.Message CreateMessageFromText(string text)
        {
            try
            {
                if (text == null)
                {
                    throw new ArgumentNullException();
                }
                
                CDO.Message message = new CDO.Message();
                this.SetMessageRaw(message, text,false);
                return message;
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        public string GetMessageSource(CDO.Message message)
        {
            try
            {
                if (message == null)
                {
                    throw new ArgumentNullException();
                }
                return this.GetMessageRaw(message);
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        public void SetMessageSource(CDO.Message message, string source)
        {
            try
            {
                if (message == null || source == null)
                {
                    throw new ArgumentNullException();
                }
                
                this.SetMessageRaw(message, source, true);
            }
            catch (Exception error)
            {
                this.WriteError(error);
                throw;
            }
        }

        string GetMessageRaw(CDO.Message message)
        {
            ADODB._Stream stream = message.GetStream();
            return stream.ReadText(stream.Size);
        }

        void SetMessageRaw(CDO.Message message, string rawMessage, bool save)
        {
            ADODB._Stream stream = message.GetStream();

            stream.Position = 0;
            stream.WriteText(rawMessage, StreamWriteEnum.stWriteChar);
            stream.SetEOS();
            stream.Flush();
            if (save)
            {
                message.DataSource.Save();
            }
        }

        //----------------------------------
        //
        //  Logging...
        //
        //----------------------------------
        
        public void WriteLog(string message)
        {
            try
            {
                if (m_log != null)
                {
                    m_log.Write(LogWriter.Info, message);
                }
            }
            catch
            {
            }
        }

        public void WriteError(string message)
        {
            try
            {
                if (m_log != null)
                {
                    m_log.WriteError(message);
                }
            }
            catch
            {
            }
        }

        void WriteError(Exception error)
        {
            try
            {
                if (m_log != null)
                {
                    m_log.WriteError(error);
                }
            }
            catch
            {
            }
        }
    }
}
